<?php
namespace bdhert\PhpBitfieldModel;

use bdhert\PhpBitfield\BitCollect;
use bdhert\PhpBitfield\BitRigger;
use bdhert\PhpBitfield\Collect;
use bdhert\PhpBitfieldModel\exception\BitFieldModelException;
use bdhert\PhpBitfieldModel\exception\OptionException;
use bdhert\PhpBitfieldModel\exception\SourceException;
use support\Db;

/**
 * 数据模型
 * Class Model
 * @method static Collect query(array $condition)                                   指定数据源
 * @method static array   get()                                                     获取数据列表
 * @method static int     count()                                                   统计数据
 * @method static array   first()                                                   获取指定数据
 * @method static int     getbit(int $index)                                        获取位状态
 * @method static bool    setbit(int $index)                                        设置位状态
 * @method static bool    delbit(int $index)                                        删除位状态
 * @method static int     bitcount()                                                统计位
 * @method static bool    increment(string $field, int $number = 1)                 某字段自增
 * @method static bool    decrement(string $field, int $number = 1)                 某字段自减
 * @method static bool    update(array $saves)                                      更新位段
 * @method static bool    insert(array $save)                                       新增位段
 * @method static Collect limit(int $page, int $limit = 0)                          分页查询条件
 * @method static Collect where($conditions = [], $operation = NULL, $value = NULL) 查询条件
 * @method static Collect desc()                                                    倒序查询
 * @method static Collect asc()                                                     顺序查询
 * @todo 待实现：事务和锁、分片摞合、数据源引擎
 * @package bdhert\PhpBitfieldModel
 */
class Model {
    // 实例配置与状态
    protected array   $fields       = [];
    protected array   $condition    = [];
    protected string  $source_table = '';
    protected string  $source_field = '';
    protected ?string $source_data  = NULL;
//    protected array   $lock_enables = [];
//    protected $lock = NULL; // 怎么实现比较好，暂时先不实现

    // 实例成员
    protected ?Collect $bitCollect = NULL;
    protected ?Agent   $agent      = NULL;

    // 全局管理
    private static array $instances   = [];
    private static array $reflections = [];
    private static $source_method     = 'query';

    public function __construct() {
        $this->agent = new Agent($this);
    }

    /**
     * 定义
     * @return mixed
     */
    public function fields() {
        [$field_map, $fields] = [array_merge(['map'], $this->fields), []];
        foreach ($field_map as $i => $v) {
            $field_index = is_numeric($i) ? 0 : $v;
            $field_name  = is_numeric($i) ? $v : $i;

            if (($field_index = BitRigger::valueFormat($field_index)) < 0)
                throw new OptionException('源字段配置错误', 400);

            $field_index = 3 * $field_index + 1 + (int)(($field_index - 1) / 3);
            $field_index > 63 && $field_index = 63;

            $fields[$field_name] = $field_index;
        }

        return $fields;
    }

    /**
     * 数据源
     * @param array $condition
     * @return Collect|null
     */
    public function source(array $condition): ?Collect {
        // 获取源数据
        if (empty($this->source_table) || empty($this->source_field))
            throw new OptionException('数据源配置错误', 400);

        $list = Db::table($this->source_table)->where($condition)->offset(0)->limit(2)->get();
        if (!($data = $list->first())) return NULL;
        if ($list->count() > 1) throw new SourceException('数据源不唯一', 400);

        try {
            $this->source_data = $data->{$this->source_field};
        } catch (\Exception $e) {
            throw new SourceException('数据库字段不存在', 400);
        }

        $fields = $this->fields();
        empty($this->source_data) && $this->source_data = BitRigger::build($fields);

        // 结构验证
        $collect = $this->bitCollect ?
            $this->bitCollect->initialize($this->source_data) : ($this->bitCollect = new BitCollect($this->source_data));

        [$field_values, $head] = [array_values($fields), $this->bitCollect->head()];
        if (count($field_values) !== $head->field->total)
            throw new OptionException('源字段配置错误', 400);
        foreach ($field_values as $field_index => $field_value) {
            if (!isset($head->field->fields[$field_index]))
                throw new OptionException('源字段配置错误', 400);

            if ($head->field->fields[$field_index] !== $field_value)
                throw new OptionException('源字段配置错误', 400);
        }

        $this->condition = $condition;

        return $collect;
    }

    /**
     * 持久化
     * @param string $string
     * @return bool
     */
    public function save(string $string) {
        try {
            Db::table($this->source_table)->where($this->condition)->update([$this->source_field => $string]);
        } catch (\Exception $e) {
            return false;
        }

        return true;
    }

    /**
     * 代理模式
     * @param $name
     * @param $arguments
     * @return mixed
     * @throws \ReflectionException
     */
    public static function __callStatic($name, $arguments) {
        if (get_class() == ($called_class = get_called_class()))
            throw new BitFieldModelException('基类不可调用', 500);

        if (!(self::$instances[$called_class] ?? NULL)) self::$instances[$called_class] = new static();
        if (!(self::$reflections[$called_class] ?? NULL))
            self::$reflections[$called_class] = new \ReflectionClass($called_class);

        if (self::$source_method === $name) {
            self::$instances[$called_class]->source(...$arguments);
            return self::$instances[$called_class];
        }

        if (!self::$instances[$called_class]->bitCollect)
            throw new BitFieldModelException('源数据未获取', 500);

        // before
        self::agentBeforeHas($called_class, $name) &&
        $arguments = call_user_func([self::$instances[$called_class]->agent, "{$name}Before"], ...$arguments);

        $res = call_user_func([self::$instances[$called_class]->bitCollect, $name], ...$arguments);

        // after
        return self::agentHas($called_class, $name) ?
            call_user_func([self::$instances[$called_class]->agent, $name], $res) : $res;
    }

    /**
     * 判断before代理方法是否存在
     * @param string $called_class
     * @param string $method
     * @return bool
     */
    private static function agentBeforeHas(string $called_class, string $method) {
        return self::agentHas($called_class, "{$method}Before");
    }

    /**
     * 判断代理方法是否存在
     * @param string $called_class
     * @param string $method
     * @return bool
     */
    private static function agentHas(string $called_class, string $method): bool {
        return (
            self::$instances[$called_class]->agent && method_exists(self::$instances[$called_class]->agent, $method)
        );
    }

    public function __call($name, $arguments) {
        return self::__callStatic($name, $arguments);
    }
}